#include <GameEngine/Graphics/Texture/texture3d.h>

#include <GameEngine/Graphics/openGL.h>



#ifdef GAMEENGINE_NAMESPACES
namespace GameEngine {
#endif

GAMEENGINE_IMPLEMENT_RTTI(Texture3d)


//! Default constructor
Texture3d::Texture3d() : id_(0), GraphicResource() {
	create_id();
}

//! Copy constructor
/*!
 @param rhs The texture to copy
 */
Texture3d::Texture3d(const Texture3d& rhs) : id_(rhs.id_), GraphicResource() {
	if ( id_ ) ResourceManager<ID,CONTEXTID>::instance()->reference_resource(*this);
}

//! Destructor
Texture3d::~Texture3d() {
	clean_id();
}

//! Assignment operator
/*!
 @param rhs The texture to copy
 */
Texture3d& Texture3d::operator=(const Texture3d& rhs) {
	if ( &rhs != this ) {
		ResourceManager<ID,CONTEXTID>* manager = (ResourceManager<ID,CONTEXTID>*)ResourceManager<ID,CONTEXTID>::instance();
		if ( id_ ) manager->unreference_resource(*this);
		id_ = rhs.id_;
		if ( id_ ) manager->reference_resource(*this);
	}
	return *this;
}

//! Check if the texture has been allocated
/*!
 @return True if the texture has been allocated, false otherwise
 */
bool Texture3d::initialized() const {
	return ( id_ != 0 );
}

//! Try to bind the texture
/*!
 @param enable_texturing If set to true, the two-dimensional texturing is enabled. Enabling this texturing
						 type should only be done once for every draw operation.
 @return True if texture could be bound, false otherwise
 */
bool Texture3d::bind_texture(bool enable_texturing) const {
	// Enable texturing if required
	if ( enable_texturing == true ) {
		glEnable(GL_TEXTURE_3D);
	}

	// Check id
	if ( id_ == 0 ) return false;

	// Bind texture id
	glBindTexture(GL_TEXTURE_3D, id_);
	return true;
}

//! Try to unbind the texture
/*!
 @param disable_texturing If set to true, the two-dimensional texturing is enabled. Disabling this texturing
						  type should only be done once after every draw operation.
 @return True if texture could be unbound, false otherwise
 */
bool Texture3d::unbind_texture(bool disable_texturing) const {
	// Disable texturing if required
	if ( disable_texturing == true ) {
		glEnable(GL_TEXTURE_3D);
	}

	// Unbind texture id
	glBindTexture(GL_TEXTURE_3D, 0);
	return true;
}

//! Set the texture application mode
/*!
 *  Texture should be bound for the parameters to take effect. Please note, however, that this
 *  function does NOT check if texture is bound or not.
 *
 @param mode The texture application mode to setup
 @return True if parameters could be setup, false otherwise
 */
bool Texture3d::set_parameters(Texture::ApplicationMode mode) {
	// Check id
	if ( id_ == 0 ) return false;
	if ( mode == Texture::UndefinedApplicationMode ) return false;

	// Set parameters
	glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, mode);
	return true;
}

//! Set the texture wrap mode
/*!
 *  Texture should be bound for the parameters to take effect. Please note, however, that this
 *  function does NOT check if texture is bound or not.
 *
 @param mode The texture wrap mode to setup
 @param wrap The texture wrap parameter to setup
 @return True if parameters could be setup, false otherwise
 */
bool Texture3d::set_parameters(Texture::WrapMode mode, WrapParameter wrap) {
	// Check id
	if ( id_ == 0 ) return false;

	// Set parameters
	if ( wrap == AllWrapParameter ) {
		glTexParameteri(GL_TEXTURE_3D, GL_TEXTURE_WRAP_S, mode);
		glTexParameteri(GL_TEXTURE_3D, GL_TEXTURE_WRAP_T, mode);
		glTexParameteri(GL_TEXTURE_3D, GL_TEXTURE_WRAP_R, mode);
	}
	else glTexParameteri(GL_TEXTURE_3D, wrap, mode);
	return true;
}

//! Set the texture magnifying filter
/*!
 *  Texture should be bound for the parameters to take effect. Please note, however, that this
 *  function does NOT check if texture is bound or not.
 *
 @param filter The texture magnifying filter to setup
 @return True if parameters could be setup, false otherwise
 */
bool Texture3d::set_parameters(Texture::MagnifyingFilter filter) {
	// Check id
	if ( id_ == 0 ) return false;
	if ( filter == Texture::UndefinedMagnifyingFilter ) return false;

	// Set parameters
	glTexParameteri(GL_TEXTURE_3D, GL_TEXTURE_MAG_FILTER, filter);
	return true;
}

//! Set the texture minifying filter
/*!
 *  Texture should be bound for the parameters to take effect. Please note, however, that this
 *  function does NOT check if texture is bound or not.
 *
 @param filter The texture minifying filter to setup
 @return True if parameters could be setup, false otherwise
 */
bool Texture3d::set_parameters(Texture::MinifyingFilter filter) {
	// Check id
	if ( id_ == 0 ) return false;
	if ( filter == Texture::UndefinedMinifyingFilter ) return false;

	// Set parameters
	glTexParameteri(GL_TEXTURE_3D, GL_TEXTURE_MIN_FILTER, filter);
	return true;
}

//! Set the texture mipmap mode
/*!
 *  Texture should be bound for the parameters to take effect. Please note, however, that this
 *  function does NOT check if texture is bound or not.
 *
 *  On most GPUs, this function should be called BEFORE texture is setup to take effect.
 *
 @param mipmap The texture mipmap mode to setup
 @return True if parameters could be setup, false otherwise
 */
bool Texture3d::set_parameters(Texture::MipmapMode mipmap) {
	// Check id
	if ( id_ == 0 ) return false;
	if ( mipmap == Texture::UndefinedMipmapMode ) return false;

	// Set parameters
	glTexParameteri(GL_TEXTURE_3D, GL_GENERATE_MIPMAP, mipmap);
	return true;
}

//! Return the OpenGL ID of the texture
/*!
 *  This ID should NOT be deleted using the glDeleteTextures() function, since the
 *  texture class takes care of its own memory and desallocates the texture when
 *  there are no more users on it.
 */
Texture3d::ID Texture3d::id() const {
	return id_;
}

//! Queries the internal format of the texture
/*!
 *  Texture should be bound for the parameters to take effect. Please note, however, that this
 *  function does NOT check if texture is bound or not.
 *
 @return The internal format of the texture
 */
Texture::TextureInternalFormat Texture3d::get_format() const {
	GLint format = -1;
	glGetTexLevelParameteriv(GL_TEXTURE_3D, 0, GL_TEXTURE_INTERNAL_FORMAT, &format);
	if ( format < 0 ) return Texture::UndefinedInternalFormat;
	return (Texture::TextureInternalFormat)format;
}

//! Queries the application mode of the texture
/*!
 *  Texture should be bound for the parameters to take effect. Please note, however, that this
 *  function does NOT check if texture is bound or not.
 *
 @return The application mode of the texture
 */
Texture::ApplicationMode Texture3d::get_application_mode() const {
	GLint mode = -1;
	glGetTexEnviv(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, &mode);
	if ( mode < 0 ) return Texture::UndefinedApplicationMode;
	return (Texture::ApplicationMode)mode;
}

//! Queries the magnifying filter of the texture
/*!
 *  Texture should be bound for the parameters to take effect. Please note, however, that this
 *  function does NOT check if texture is bound or not.
 *
 @return The magnifying filter of the texture
 */
Texture::MagnifyingFilter Texture3d::get_magnifying_filter() const {
	GLint filter = -1;
	glGetTexParameteriv(GL_TEXTURE_3D, GL_TEXTURE_MAG_FILTER, &filter);
	if ( filter < 0 ) return Texture::UndefinedMagnifyingFilter;
	return (Texture::MagnifyingFilter)filter;
}

//! Queries the minifying filter of the texture
/*!
 *  Texture should be bound for the parameters to take effect. Please note, however, that this
 *  function does NOT check if texture is bound or not.
 *
 @return The minifying filter of the texture
 */
Texture::MinifyingFilter Texture3d::get_minifying_filter() const {
	GLint filter = -1;
	glGetTexParameteriv(GL_TEXTURE_3D, GL_TEXTURE_MIN_FILTER, &filter);
	if ( filter < 0 ) return Texture::UndefinedMinifyingFilter;
	return (Texture::MinifyingFilter)filter;
}

//! Queries the mipmap mode of the texture
/*!
 *  Texture should be bound for the parameters to take effect. Please note, however, that this
 *  function does NOT check if texture is bound or not.
 *
 @return The mipmap mode of the texture
 */
Texture::MipmapMode Texture3d::get_mipmap_mode() const {
	GLint mode = -1;
	glGetTexParameteriv(GL_TEXTURE_3D, GL_GENERATE_MIPMAP, &mode);
	if ( mode < 0 ) return Texture::UndefinedMipmapMode;
	return (Texture::MipmapMode)mode;
}

//! Queries the width of the texture
/*!
 *  Texture should be bound for the parameters to take effect. Please note, however, that this
 *  function does NOT check if texture is bound or not.
 *
 @return The width of the texture
 */
Texture::TextureSize Texture3d::get_width() const {
	GLint width = -1;
	glGetTexLevelParameteriv(GL_TEXTURE_3D, 0, GL_TEXTURE_WIDTH, &width);
	if ( width < 0 ) return 0;
	return (Texture::TextureSize)width;
}

//! Queries the height of the texture
/*!
 *  Texture should be bound for the parameters to take effect. Please note, however, that this
 *  function does NOT check if texture is bound or not.
 *
 @return The height of the texture
 */
Texture::TextureSize Texture3d::get_height() const {
	GLint height = -1;
	glGetTexLevelParameteriv(GL_TEXTURE_3D, 0, GL_TEXTURE_HEIGHT, &height);
	if ( height < 0 ) return 0;
	return (Texture::TextureSize)height;
}

//! Queries the depth of the texture
/*!
 *  Texture should be bound for the parameters to take effect. Please note, however, that this
 *  function does NOT check if texture is bound or not.
 *
 @return The depth of the texture
 */
Texture::TextureSize Texture3d::get_depth() const {
	GLint depth = -1;
	glGetTexLevelParameteriv(GL_TEXTURE_3D, 0, GL_TEXTURE_DEPTH, &depth);
	if ( depth < 0 ) return 0;
	return (Texture::TextureSize)depth;
}

//! Fill the texture with the input color array
/*!
 *  Caution: make sure that the colors array has a size equal to width*height*depth. If the size of the
 *  colors array is smaller than width*height*depth, this function is likely to result in an application
 *  crash. If the size of the colors array is larger than width*height*depth, all colors after the
 *  (width*height*depth)-th one will be ignored.
 *
 *  Texture should be bound for the parameters to take effect. Please note, however, that this
 *  function does NOT check if texture is bound or not.
 *
 @param colors The array of colors that should be used to fill in the texture.
 @param width The width of the array of colors.
 @param height The height of the array of colors.
 @param depth The depth of the array of colors.
 @param format The internal format for the texture.
 @return True if texture was filled, false otherwise.
 */
bool Texture3d::set_texture(ColorRGB* colors, Texture::TextureSize width, Texture::TextureSize height, Texture::TextureSize depth, Texture::TextureInternalFormat format) {
	// Check input parameters
	if ( colors == nil ) return false;
	if ( width <= 0 ) return false;
	if ( height <= 0 ) return false;
	if ( depth <= 0 ) return false;
	if ( format == Texture::UndefinedInternalFormat ) return false;

	// Setup texture
	glTexImage3D( GL_TEXTURE_3D, 0, format, width, height, depth, 0, GL_RGB, GL_FLOAT, colors );
	return true;
}

//! Fill the texture with the input color array
/*!
 *  Caution: make sure that the colors array has a size equal to width*height*depth. If the size of the
 *  colors array is smaller than width*height*depth, this function is likely to result in an application
 *  crash. If the size of the colors array is larger than width*height*depth, all colors after the
 *  (width*height*depth)-th one will be ignored.
 *
 *  Texture should be bound for the parameters to take effect. Please note, however, that this
 *  function does NOT check if texture is bound or not.
 *
 @param colors The array of colors that should be used to fill in the texture.
 @param width The width of the array of colors.
 @param height The height of the array of colors.
 @param depth The depth of the array of colors.
 @param format The internal format for the texture.
 @return True if texture was filled, false otherwise.
 */
bool Texture3d::set_texture(ColorRGBA* colors, Texture::TextureSize width, Texture::TextureSize height, Texture::TextureSize depth, Texture::TextureInternalFormat format) {
	// Check input parameters
	if ( colors == nil ) return false;
	if ( width <= 0 ) return false;
	if ( height <= 0 ) return false;
	if ( depth <= 0 ) return false;
	if ( format == Texture::UndefinedInternalFormat ) return false;

	// Setup texture
	glTexImage3D( GL_TEXTURE_3D, 0, format, width, height, depth, 0, GL_RGBA, GL_FLOAT, colors );
	return true;
}

//! Clean the previous ID
void Texture3d::clean_id() {
	// Nothing to do if ID is not allocated
	if ( id_ == 0 ) return;

	// Unreference ID from the resource manager
	ResourceManager<ID,CONTEXTID>::instance()->unreference_resource(*this);

	// Clean ID member variable
	id_ = 0;
}

//! Allocate a new ID
void Texture3d::create_id() {
	// Clean previous ID, if any
	clean_id();

	// Generate a new texture ID
	glGenTextures(1, &id_);

	// Reference this ID
	ResourceManager<ID,CONTEXTID>::instance()->reference_resource(*this);
}

//! Desallocate the texture ID
void Texture3d::destroy_resource() const {
	glDeleteTextures(1, &id_);
}

//! Return the texture context
Texture3d::CONTEXTID Texture3d::context() const {
	return rtti_id_;
}

//! Return the texture reference
Texture3d::ID Texture3d::reference() const {
	return id_;
}

#ifdef GAMEENGINE_NAMESPACES
} // namespace GameEngine
#endif
